/* 
 * File:   main.cpp
 * Author: sysel
 * Description:
 *      Все данные, полученные от объектов "складируются" и перенаправляются
 *      на все подключенные отображалки. По запросу отображалки, высылаются
 *      данные за определённый период времени. Форматы сообщений описаны ниже.
 *
 * Created on 13 Март 2011 г., 14:19
 */

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <dirent.h>
#include <errno.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <syslog.h>

#include <map>
#include <iostream>

#include "general.h"
#include "BackupRobot.h"

int startListen(int port);
void recvserv(int sock);
void sendserv(int sock);
int _recieveClientData(int sock, sockaddr_in addr);
void catcher(int sig);

int pipefd[2];
BackupRobot *backupRobot = 0;

int recvsock, sendsock;

/*
 * 
 */
int main(int argc, char** argv) {
	if (argc != 2) {
		printf("Usage: gpsmond port_to_listen");
		return 1;
	}

	int port = atoi(argv[1]);
	if (0 == port) { // ошибка ввода
		printf("Usage: gpsmond port_to_listen");
		return 1;
	}

	openlog("gpsmond", LOG_CONS | LOG_PID, LOG_USER);

	signal(SIGTERM, catcher);
	//signal(SIGKILL, catcher);
	signal(SIGINT, catcher);
	signal(SIGABRT, catcher);
	signal(SIGSEGV, catcher);
	// daemonize
	/*if(daemon(0,1) == -1) { // cause and error
		syslog(LOG_ERR, "Error while establish daemon mode. errno = %d", errno);
		return 1;
	}
	syslog(LOG_INFO, "Daemon started. УРА!");*/

	backupRobot = BackupRobot::Instance();

	recvsock = startListen(GETPORT);
	sendsock = startListen(SENDPORT);
	if (recvsock == -1 || sendsock == -1) {
		close(recvsock);
		close(sendsock);
		return 2;
	}

	syslog(LOG_INFO, "Server side was successful established");

	// создаём канал
	if (0 != pipe(pipefd)) {
		syslog(LOG_ERR, "Error pipe.");
		return 1;
	}
	// расщепляемся
	int pid = fork();
	if (pid < 0) {
		syslog(LOG_ERR, "Fork Error. Quit.");
		return 1;
	} else if (0 == pid) { // child
		// только принимающая сторона
		close(pipefd[1]);
		sendserv(sendsock);
		close(pipefd[0]);
	} else { // parent
		// только на запись
		close(pipefd[0]);
		recvserv(recvsock);
		close(pipefd[1]);
		exit(0);
	}


	closelog();
	return (EXIT_SUCCESS);
}

void catcher(int sig) {
	syslog(LOG_INFO, "Catch the %d signal. Bye", sig);
	close(recvsock);
	close(sendsock);
	exit(0);
}

/************************************************
 * Начинает слушать порт
 * @param port Порт для прослушки
 * @return Socket
 */
int startListen(int port) {
	int sock = socket(AF_INET, SOCK_STREAM, 0);
	fcntl(sock, F_SETFL, O_NONBLOCK);
	if (sock < 0) {
		// error while creating a socket
		syslog(LOG_ERR, "Error creating a socket. errno = %d", errno);
		return -1;
	}

	struct sockaddr_in servaddr;
	servaddr.sin_family = AF_INET;
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servaddr.sin_port = htons(port);

	if (0 != bind(sock, (sockaddr*) (&servaddr), sizeof (servaddr))) {
		syslog(LOG_ERR, "Error binding to socket. errno = %d", errno);
		return -1;
	}

	if (0 != listen(sock, SOMAXCONN)) {
		syslog(LOG_ERR, "Error listen socket. errno = %d", errno);
		return -1;
	}
	return sock;
}

/************************************************
 * Работа сервера по объектам слежения
 */
void recvserv(int sock) {
	// цикл, ожидающий подкдючения и читающий данные
	//  в теории - ничего не блокируется, поэтому возможно использование
	//  одного потока.
	ClientsMap clients;
	int maxfd = sock + 1;	

	while (1) {
		// проверка возможности чтения.
		// без ожидания

		fd_set fsset; // набор битов для ф-ии select

		FD_ZERO(&fsset); // обнуляем биты
		FD_SET(sock, &fsset); // устанавливаем бит для слушающего сокета

		printf("A\n");

		// биты для клиентов
		for (ClientsMap::iterator i = clients.begin(); i != clients.end(); i++) {
			FD_SET(i->first, &fsset);
		}

		timeval to;
		to.tv_sec = 5;
		to.tv_usec = 0;

		int retval = select(maxfd+1, &fsset, 0, 0, &to);

		if (-1 == retval) { // ашыпка
			syslog(LOG_ERR, "Select function error. Errno = %d", errno);
			sleep(1);
			continue;
		} else if (retval > 0) { // Some connected or data accepted
			if (FD_ISSET(sock, &fsset)) { // если есть данные в слушающем сокете
				// то подключаем клиента
				struct sockaddr_in cliaddr;
				socklen_t cliaddr_size = sizeof (cliaddr);
				int client_sock = accept(sock, (sockaddr*) (&cliaddr), &cliaddr_size);
				fcntl(client_sock, F_SETFL, O_NONBLOCK);

				// устанавливаем слушающий бит для него
				//FD_SET(client_sock, &fsset);

				if (client_sock > maxfd)
					maxfd = client_sock;

				syslog(LOG_INFO, "Device connected from %s!", inet_ntoa(cliaddr.sin_addr));

#ifdef _DEBUG_
				printf("add to clients %d\n", client_sock);
				printf("clients size %d\n", clients.size());
#endif
				clients[client_sock] = cliaddr;
#ifdef _DEBUG_
				printf("clients size %d\n", clients.size());
#endif
			} else {
#ifdef _DEBUG_
				printf("we are in else!!!");
#endif
				// бежим по всем клиентам
				for (ClientsMap::iterator i = clients.begin(); i != clients.end(); i++) {
					if (FD_ISSET(i->first, &fsset)) { // имеем что-то с клиента
						if (1 == _recieveClientData(i->first, i->second)) {
							syslog(LOG_INFO, "Client %s disconnected.", inet_ntoa(i->second.sin_addr));
							// то отключаемся
							FD_CLR(i->first, &fsset);
							close(i->first);
							clients.erase(i);
						}
					}
				}
			}

		}
		// else nothing connected		
		//sleep(100);
	}
}

/************************************************
 * Принимает клиентов-отображалок
 */
void sendserv(int sock) {
	ClientsMap clients;

	int maxfd = sock > pipefd[0] ? sock : pipefd[0];
	fd_set fsset; // набор битов для ф-ии select
	FD_ZERO(&fsset); // обнуляем биты
	FD_SET(sock, &fsset); // устанавливаем бит для слушающего сокета
	FD_SET(pipefd[0], &fsset); // устанавливаем бит для пайпа

	while (true) {
		// проверка возможности чтения.
		int retval = select(maxfd, &fsset, 0, 0, 0);

		if (-1 == retval) { // ашыпка
			syslog(LOG_ERR, "Select function error. Errno = %d", errno);
			sleep(100);
			continue;
		} else if (retval > 0) { // Some connected or data accepted
			if (FD_ISSET(sock, &fsset)) { // если есть данные в слушающем сокете
				// то подключаем клиента
				struct sockaddr_in cliaddr;
				socklen_t cliaddr_size = sizeof (cliaddr);
				int client_sock = accept(sock, (sockaddr*) (&cliaddr), &cliaddr_size);

				syslog(LOG_INFO, "Client-shower connected from %s!", inet_ntoa(cliaddr.sin_addr));
				clients[client_sock] = cliaddr;
			} else if (FD_ISSET(pipefd[0], &fsset)) { // пришло от "того" потока
				// читаем координаты и УИД и передаем тупо всем клиентам
				//char buf[21];
				Message message;
				if (sizeof (Message) != read(pipefd[0], (char*) & message, sizeof (Message))) {
					// какая-то херня с данными. Ругаемся, но пропускаем
					syslog(LOG_WARNING, "[SendServ]Some shit with data from other thread!");
					continue;
				}
				message.uid = 0;

				for (ClientsMap::const_iterator i = clients.begin(); i != clients.end(); i++)
					send(i->first, &message, sizeof (Message), MSG_DONTWAIT);
			}
		}
	}
}

/************************************************
 * Получает и обрабатывает данные от объекта слежения.
 */

int _recieveClientData(int sock, sockaddr_in addr) {
	//char *buf = new char[21];
	//const int packetsize = 21;
#ifdef _DEBUG_
	printf("We are in recieve()func\n");
#endif
	Message message;
	int recvsize = read(sock, (char *) & message, sizeof (Message));

	if(0 == recvsize) { // disconnected
		syslog(LOG_INFO, "Client device %s disconnected.", inet_ntoa(addr.sin_addr));
			return 1;
	}

	if (sizeof (Message) != recvsize) {
		syslog(LOG_ERR, "Data length error from %s. Error size = %d bytes.",
				inet_ntoa(addr.sin_addr), recvsize);
		return -1;
	}

	syslog(LOG_INFO, "Data recieved from %s. Length = %d",
			inet_ntoa(addr.sin_addr), recvsize);

	switch (message.code) {
		case 1:
		{
			Uid uidloc = backupRobot->getUniqueID();
			syslog(LOG_INFO, "Send to %s. Data = %d", inet_ntoa(addr.sin_addr), uidloc);
			send(sock, (const void *) (&uidloc), sizeof (Uid), MSG_DONTWAIT); // чтобы не блокировалось
			break;
		}
		case 2:
		{
			Uid uid = message.uid;
			double latt = message.latt;
			double lont = message.lont;

			// разделяемся, чтобы не работать с файлом в одном потоке
			syslog(LOG_INFO, "Recieve data from %s device. Backup it %d: %f x %f",
					inet_ntoa(addr.sin_addr), uid, latt, lont);
#ifdef _DEBUG_
			printf("trying to fork\n");
#endif
			int pida = fork();
			if (0 == pida) { // child
				try {
#ifdef _DEBUG_
					printf("trying to backup\n");
#endif
					backupRobot->logData(message);
				} catch (char*mes) {
					syslog(LOG_ERR, "logData throws an error: %s", mes);
				}
				//Send ok
				send(sock, "ok", 2, MSG_DONTWAIT);
				exit(0);
			} else if (pida > 0) { // папа.
#ifdef _DEBUG_
				printf("Write message to pipe\n");
#endif
				// отправляем в пайп
				write(pipefd[1], (char*) & message, 20);
			} else
				exit(0);
			break;
		}
		case 3:
			syslog(LOG_INFO, "Client device %s disconnected.", inet_ntoa(addr.sin_addr));
			return 1;
			break;
		default:
			return -1;
			break;
	}

}
